#include "Object.h"
#include "Texture.h"
#include "Model.h"
#include "Shader.h"
#include "Vertex.h"
#include "string.h"

//---------------------------------------------------
/// Constructor
//---------------------------------------------------
Object::Object(void)
: m_position(0.0f, 0.0f, 0.0f), 
m_rotation(0.0f, 0.0f, 0.0f), 
m_scale(1.0f, 1.0f, 1.0f)
{
	m_texture = 0;
	m_model = 0;
	m_shader = 0;
	m_id[0] = '\0';

	CalculateWorldMatrix();
}

//---------------------------------------------------
/// Init
//---------------------------------------------------
void Object::Init(const char * id, const Vector3 & pos, const Vector3 & rot, const Vector3 & sca, Texture* texture,  Model* model, Shader* shader)
{
	//init id
	strcpy(m_id, id);

	m_position = pos;
	m_rotation = rot;
	m_scale = sca;
	CalculateWorldMatrix();

	m_texture = texture;
	m_model = model;
	m_shader = shader;
}

//---------------------------------------------------
/// Calculate object's World Matrix
//---------------------------------------------------
void Object::CalculateWorldMatrix()
{
	Matrix rotation, scale, translation;

	rotation.SetRotation(m_rotation);
	scale.SetScale(m_scale);
	translation.SetTranslation(m_position);

	m_worldMatrix = scale * rotation * translation;
}

//---------------------------------------------------
/// DRAW OBJECT
//---------------------------------------------------
void Object::Draw(Matrix & vpMatrix)
{
	// get texture target
	GLuint target = m_texture->GetTarget();

	// use program
	glUseProgram(m_shader->program);

	// Calculate WVP matrix
	Matrix wvpMatrix;
	wvpMatrix = m_worldMatrix * vpMatrix;

	// binding buffer
	glBindBuffer(GL_ARRAY_BUFFER, m_model->GetVboId());
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_model->GetIboId());

	// target == GL_TEXTURE_2D
	if (target == GL_TEXTURE_2D)
	{
		if (m_shader->a_position != -1)
		{
			glEnableVertexAttribArray(m_shader->a_position);
			glVertexAttribPointer(m_shader->a_position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, pos));
		}

		if (m_shader->a_color != -1)
		{
			glEnableVertexAttribArray(m_shader->a_color);
			glVertexAttribPointer(m_shader->a_color, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, color));
		}

		if (m_shader->u_WVP != -1)
		{
			glUniformMatrix4fv(m_shader->u_WVP, 1, 0, (float*)(&wvpMatrix));

		}

		if (m_shader->a_uv != 1)
		{
			glEnableVertexAttribArray(m_shader->a_uv);
			glVertexAttribPointer(m_shader->a_uv, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, uv));
		}
	}
	// target = GL_TEXTURE_CUBE_MAP
	else
	{
		if (m_shader->a_CubeVertexPos != -1)
		{
			glEnableVertexAttribArray(m_shader->a_CubeVertexPos);
			glVertexAttribPointer(m_shader->a_CubeVertexPos, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, pos));
		}

		if (m_shader->u_CubeMVPMatrix != -1)
		{
			glUniformMatrix4fv(m_shader->u_CubeMVPMatrix, 1, 0, (float*)(&wvpMatrix));

		}
	}

	// make active a texture unit
	unsigned short textureUnit = 0;
	glActiveTexture(GL_TEXTURE0 + textureUnit); 
	// bind the texture to the currently active texture unit
	glBindTexture(target, m_texture->GetTextureId());

	// set the uniform u_texture
	// (target == GL_TEXTURE_2D)
	if (m_shader->u_texture != -1)
	{
		glUniform1i(m_shader->u_texture, textureUnit);
	}
	// (target == GL_TEXTURE_CUBE_MAP)
	if (m_shader->u_samplerCubeMap != -1)
	{
		glUniform1i(m_shader->u_samplerCubeMap, textureUnit);
	}
	
	m_shader->EnableState();
	glDrawElements(GL_TRIANGLES, m_model->GetNumOfIndices(), GL_UNSIGNED_SHORT, 0);
	m_shader->DisableState();

	// bind -> 0
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindTexture(target, 0);

}

void Object::Draw(Matrix & vpMatrix, Vector4 & color)
{
	// use program
	glUseProgram(m_shader->program);

	// Calculate WVP matrix
	Matrix wvpMatrix;
	wvpMatrix = m_worldMatrix * vpMatrix;

	// binding buffer
	glBindBuffer(GL_ARRAY_BUFFER, m_model->GetVboId());
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_model->GetIboId());
	
	if (m_shader->a_position != -1)
	{
		glEnableVertexAttribArray(m_shader->a_position);
		glVertexAttribPointer(m_shader->a_position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, pos));
	}

	if (m_shader->u_WVP != -1)
	{
		glUniformMatrix4fv(m_shader->u_WVP, 1, 0, (float*)(&wvpMatrix));

	}

	if (m_shader->u_color != -1)
	{
		glUniform4fv(m_shader->u_color, 1, (float*)(&color));

	}
	
	m_shader->EnableState();
	glDrawElements(GL_TRIANGLES, m_model->GetNumOfIndices(), GL_UNSIGNED_SHORT, 0);
	m_shader->DisableState();

	// bind -> 0
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

//---------------------------------------------------
/// UPDATE
//---------------------------------------------------
void Object::Update()
{

}

//---------------------------------------------------
/// These functions follow are setters
//---------------------------------------------------
void Object::SetPosition(const Vector3 & pos)
{
	m_position = pos;
	CalculateWorldMatrix();
}

void Object::SetRotation(const Vector3 & rot)
{
	m_rotation = rot;
	CalculateWorldMatrix();
}

void Object::SetScale(const Vector3 & sca)
{
	m_scale = sca;
	CalculateWorldMatrix();
}

void Object::SetTexture(Texture *& texture)
{
	m_texture = texture;
}

void Object::SetModel( Model *& model)
{
	m_model = model;
}

void Object::SetShader(Shader *& shader)
{
	m_shader = shader;
}

//---------------------------------------------------
/// These functions follow are getters
//---------------------------------------------------
const char* Object::GetId()
{
	return m_id;
}

Vector3 & Object::GetPosition()
{
	return m_position;
}

Vector3 & Object::GetRotation()
{
	return m_rotation;
}

Vector3 & Object::GetScale()
{
	return m_scale;
}

Texture *& Object::GetTexture()
{
	return m_texture;
}

Model *& Object::GetModel()
{
	return m_model;
}

Shader *& Object::GetShader()
{
	return m_shader;
}

//---------------------------------------------------
/// Deconstructor
//---------------------------------------------------
Object::~Object(void)
{

}
